#include "TwOgreWindowManager.h"

#include "TwOgreVariable.h"
#include "TwOgreWindow.h"

#include "OgreException.h"

template<> TwOgre::WindowManager* Ogre::Singleton<TwOgre::WindowManager>::ms_Singleton = 0;

namespace TwOgre{
	WindowManager::WindowManager(Ogre::RenderWindow *renderWindow, Ogre::SceneManager *sceneManager) :
		mRenderWindow(renderWindow), mSceneManager(sceneManager), mKeyModifiers(TW_KMOD_NONE)
	{
		// Try to get the Direct3D Device from the RenderWindow
		void *d3dDevice = 0;
		mRenderWindow->getCustomAttribute("D3DDEVICE", &d3dDevice);

		if(!d3dDevice)
		{
			// The D3DDevice came back null so we're in an OpenGL window
			TwInit(TW_OPENGL, NULL);
		}
		else
		{
			// Pass the D3DDevice to AntTweakBar
			TwInit(TW_DIRECT3D9, d3dDevice);
		}

		// Inform AntTweakBar of the current window size
		TwWindowSize(mRenderWindow->getWidth(), mRenderWindow->getHeight());

		// Register for events so we can render ourselves properly
		mSceneManager->addRenderQueueListener(this);
		Ogre::WindowEventUtilities::addWindowEventListener(mRenderWindow, this);
	}

	WindowManager::~WindowManager()
	{
		// Make sure all TwOgre::Windows were destroyed
		WindowMap::iterator winItr;
		for(winItr = mWindowMap.begin(); winItr != mWindowMap.end(); winItr++)
		{
			delete winItr->second;
		}
		mWindowMap.clear();

		// Shut down AntTweakBar
		TwTerminate();

		// Unregister for events
		Ogre::WindowEventUtilities::removeWindowEventListener(mRenderWindow, this);
		mSceneManager->removeRenderQueueListener(this);
	}

	WindowManager* WindowManager::getSingletonPtr(void)
	{
		return ms_Singleton;
	}

	WindowManager& WindowManager::getSingleton(void)
	{  
		assert( ms_Singleton );  return (*ms_Singleton);  
	}

	Window* WindowManager::createWindow(const Ogre::String& name, const Ogre::String& title,
		const Ogre::ColourValue& windowColor, const Ogre::String& help)
	{
		// See if there is already a TwOgre::Window with this name
		WindowMap::const_iterator winItr = mWindowMap.find(name);
		if(winItr != mWindowMap.end())
		{
			throw Ogre::Exception(Ogre::Exception::ERR_DUPLICATE_ITEM, "TwOgre::Window " + name + " already exists!",
				"TwOgre::WindowManager::createWindow");
		}

		// Create the new Window and push it into our map, then return it
		Window *newWindow = new Window(name, title, windowColor, help);
		mWindowMap[name] = newWindow;
		return newWindow;
	}

	Window* WindowManager::createWindow(WindowParams& params)
    {
		// See if there is already a TwOgre::Window with this name
		WindowMap::const_iterator winItr = mWindowMap.find(params.name_);
		if(winItr != mWindowMap.end())
		{
			throw Ogre::Exception(Ogre::Exception::ERR_DUPLICATE_ITEM, "TwOgre::Window " + params.name_ + " already exists!",
				"TwOgre::WindowManager::createWindow");
		}

		// Create the new Window and push it into our map, then return it
        Window *newWindow = new Window(params);
		mWindowMap[params.name_] = newWindow;
		return newWindow;
    }
	
    void WindowManager::destroyWindow(const Ogre::String& name)
	{
		// See if there is a TwOgre::Window by this name in the map
		WindowMap::iterator winItr = mWindowMap.find(name);
		if(winItr == mWindowMap.end())
		{
			throw Ogre::Exception(Ogre::Exception::ERR_ITEM_NOT_FOUND, "TwOgre::Window " + name + " not found for destruction!",
				"TwOgre::WindowManager::destroyWindow");
		}

		// If we found the window then destroy it and remove it's entry from the map
		delete winItr->second;
		mWindowMap.erase(winItr);
	}

	void WindowManager::destroyWindow(Window *window)
	{
		// Look for this window in the map
		WindowMap::iterator winItr;
		for(winItr = mWindowMap.begin(); winItr != mWindowMap.end(); winItr++)
		{
			if(winItr->second == window)
			{
				// Remove this entry from the map and stop looking
				mWindowMap.erase(winItr);
				break;
			}
		}

		// We delete this outside of the loop becuase we want to destroy it
		// even if it wasn't in our map for some reason
		delete window;
	}

	void WindowManager::setFontSize(FontSize size)
	{
		/*
		** All of the windows use the same font size so we are just
		** going to grabe the first one in the map and set its
		** size, which will then be applied to all of them.
		*/
		if(mWindowMap.empty())
			return;

		std::stringstream ss;
		ss << " '" << mWindowMap.begin()->first << "' FontSize='" << size << "' ";
		TwDefine(ss.str().c_str());
	}

	bool WindowManager::injectMouseMoved(const OIS::MouseEvent &arg)
	{
		/*
		** We want to return true if this event was handled, so we
		** return true only if both TwMouseMotion and TwMouseWheel
		** return 0
		*/
		return (TwMouseMotion(arg.state.X.abs, arg.state.Y.abs) == 0 &&
				TwMouseWheel(arg.state.Z.abs) == 0);
	}

	bool WindowManager::injectMousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
	{
		// Pass the event on to AntTweakBar and return true if the event was handled
		return TwMouseButton(TW_MOUSE_PRESSED, convertOIStoTweakMouse(id)) == 0;
	}

	bool WindowManager::injectMouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
	{
		// Pass the event on to AntTweakBar and return true if the event was handled
		return TwMouseButton(TW_MOUSE_RELEASED, convertOIStoTweakMouse(id)) == 0;
	}

	bool WindowManager::injectKeyPressed(const OIS::KeyEvent &arg)
	{
		// Check for any modifier keys being pressed and store the result
		switch(arg.key)
		{
		case OIS::KC_LSHIFT:
		case OIS::KC_RSHIFT:
			mKeyModifiers |= TW_KMOD_SHIFT;
			break;
		case OIS::KC_LCONTROL:
		case OIS::KC_RCONTROL:
			mKeyModifiers |= TW_KMOD_CTRL;
			break;
		case OIS::KC_LMENU:
		case OIS::KC_RMENU:
			mKeyModifiers |= TW_KMOD_ALT;
			break;
		}
		
		// Pass the event on to AntTweakBar and return true if the event was handled
		return TwKeyPressed(convertOIStoTweakKey(arg), mKeyModifiers) == 0;
	}

	bool WindowManager::injectKeyReleased(const OIS::KeyEvent &arg)
	{
		// Check for any modifier keys being released and store the result
		switch(arg.key)
		{
		case OIS::KC_LSHIFT:
		case OIS::KC_RSHIFT:
			mKeyModifiers &= ~TW_KMOD_SHIFT;
			return true;
			break;
		case OIS::KC_LCONTROL:
		case OIS::KC_RCONTROL:
			mKeyModifiers &= ~TW_KMOD_CTRL;
			return true;
			break;
		case OIS::KC_LMENU:
		case OIS::KC_RMENU:
			mKeyModifiers &= ~TW_KMOD_ALT;
			return true;
			break;
		}

		return false;
	}

	void WindowManager::renderQueueStarted(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& skipThisInvocation)
	{
		// We're going to render ourselves in the Overlay Queue so we're always on top
		if(queueGroupId == Ogre::RENDER_QUEUE_OVERLAY)
		{
			TwDraw();
		}
	}

	void WindowManager::windowResized(Ogre::RenderWindow* renderWindow)
	{
		// If this event really is for our window, then inform AntTweakBar of the new size
		if(renderWindow == mRenderWindow)
		{
			TwWindowSize(mRenderWindow->getWidth(), mRenderWindow->getHeight());
		}
	}

	inline TwMouseButtonID WindowManager::convertOIStoTweakMouse(OIS::MouseButtonID id) const
	{
		// Translate the incoming OIS mouse button to the AntTweakBar equivalent
		switch(id)
		{
		case OIS::MB_Left:
			return TW_MOUSE_LEFT;
			break;
		case OIS::MB_Middle:
			return TW_MOUSE_MIDDLE;
			break;
		case OIS::MB_Right:
			return TW_MOUSE_RIGHT;
			break;
		default:
			return TW_MOUSE_LEFT;
			break;
		};
	}

	int WindowManager::convertOIStoTweakKey(const OIS::KeyEvent& keyEvent) const
	{
		// Is the text already valid ascii?
		if((keyEvent.text > 0x40 && keyEvent.text < 0x91) ||
		   (keyEvent.text > 0x61 && keyEvent.text < 0x7B) ||
		   (keyEvent.text > 0x2F && keyEvent.text < 0x3A))
		   return keyEvent.text;

		// Translate the incoming OIS Key to the AntTweakBar equivalent
		switch(keyEvent.key)
		{
		case OIS::KC_BACK:
			return TW_KEY_BACKSPACE;
			break;
		case OIS::KC_TAB:
			return TW_KEY_TAB;
			break;
		case OIS::KC_RETURN:
			return TW_KEY_RETURN;
			break;
		case OIS::KC_PAUSE:
			return TW_KEY_PAUSE;
			break;
		case OIS::KC_ESCAPE:
			return TW_KEY_ESCAPE;
			break;
		case OIS::KC_SPACE:
			return TW_KEY_SPACE;
			break;
		case OIS::KC_DELETE:
			return TW_KEY_DELETE;
			break;
		case OIS::KC_UP:
			return TW_KEY_UP;
			break;
		case OIS::KC_DOWN:
			return TW_KEY_DOWN;
			break;
		case OIS::KC_RIGHT:
			return TW_KEY_RIGHT;
			break;
		case OIS::KC_LEFT:
			return TW_KEY_LEFT;
			break;
		case OIS::KC_INSERT:
			return TW_KEY_INSERT;
			break;
		case OIS::KC_HOME:
			return TW_KEY_HOME;
			break;
		case OIS::KC_END:
			return TW_KEY_END;
			break;
		case OIS::KC_PGUP:
			return TW_KEY_PAGE_UP;
			break;
		case OIS::KC_PGDOWN:
			return TW_KEY_PAGE_DOWN;
			break;
		case OIS::KC_F1:
			return TW_KEY_F1;
			break;
		case OIS::KC_F2:
			return TW_KEY_F2;
			break;
		case OIS::KC_F3:
			return TW_KEY_F3;
			break;
		case OIS::KC_F4:
			return TW_KEY_F4;
			break;
		case OIS::KC_F5:
			return TW_KEY_F5;
			break;
		case OIS::KC_F6:
			return TW_KEY_F6;
			break;
		case OIS::KC_F7:
			return TW_KEY_F7;
			break;
		case OIS::KC_F8:
			return TW_KEY_F8;
			break;
		case OIS::KC_F9:
			return TW_KEY_F9;
			break;
		case OIS::KC_F10:
			return TW_KEY_F10;
			break;
		case OIS::KC_F11:
			return TW_KEY_F11;
			break;
		case OIS::KC_F12:
			return TW_KEY_F12;
			break;
		case OIS::KC_F13:
			return TW_KEY_F13;
			break;
		case OIS::KC_F14:
			return TW_KEY_F14;
			break;
		case OIS::KC_F15:
			return TW_KEY_F15;
			break;
		// Start checking against ASCII values
		case OIS::KC_1:
			return '1';
			break;
		case OIS::KC_2:
			return '2';
			break;
		case OIS::KC_3:
			return '3';
			break;
		case OIS::KC_4:
			return '4';
			break;
		case OIS::KC_5:
			return '5';
			break;
		case OIS::KC_6:
			return '6';
			break;
		case OIS::KC_7:
			return '7';
			break;
		case OIS::KC_8:
			return '8';
			break;
		case OIS::KC_9:
			return '9';
			break;
		case OIS::KC_0:
			return '0';
			break;
		case OIS::KC_MINUS:
			return '-';
			break;
		case OIS::KC_EQUALS:
			return '=';
			break;
		case OIS::KC_Q:
			return 'q';
			break;
		case OIS::KC_W:
			return 'w';
			break;
		case OIS::KC_E:
			return 'e';
			break;
		case OIS::KC_R:
			return 'r';
			break;
		case OIS::KC_T:
			return 't';
			break;
		case OIS::KC_Y:
			return 'y';
			break;
		case OIS::KC_U:
			return 'u';
			break;
		case OIS::KC_I:
			return 'i';
			break;
		case OIS::KC_O:
			return 'o';
			break;
		case OIS::KC_P:
			return 'p';
			break;
		case OIS::KC_LBRACKET:
			return '[';
			break;
		case OIS::KC_RBRACKET:
			return ']';
			break;
		case OIS::KC_A:
			return 'a';
			break;
		case OIS::KC_S:
			return 's';
			break;
		case OIS::KC_D:
			return 'd';
			break;
		case OIS::KC_F:
			return 'f';
			break;
		case OIS::KC_G:
			return 'g';
			break;
		case OIS::KC_H:
			return 'h';
			break;
		case OIS::KC_J:
			return 'j';
			break;
		case OIS::KC_K:
			return 'k';
			break;
		case OIS::KC_L:
			return 'l';
			break;
		case OIS::KC_SEMICOLON:
			return ';';
			break;
		case OIS::KC_APOSTROPHE:
			return 0x39;
			break;
		case OIS::KC_GRAVE:
			return '^';
			break;
		case OIS::KC_BACKSLASH:
			return '\\';
			break;
		case OIS::KC_Z:
			return 'z';
			break;
		case OIS::KC_X:
			return 'x';
			break;
		case OIS::KC_C:
			return 'c';
			break;
		case OIS::KC_V:
			return 'v';
			break;
		case OIS::KC_B:
			return 'b';
			break;
		case OIS::KC_N:
			return 'n';
			break;
		case OIS::KC_M:
			return 'm';
			break;
		case OIS::KC_COMMA:
			return ',';
			break;
		case OIS::KC_PERIOD:
			return '.';
			break;
		case OIS::KC_SLASH:
			return '/';
			break;
		case OIS::KC_MULTIPLY:
			return '*';
			break;
		case OIS::KC_NUMPAD7:
			return '7';
			break;
		case OIS::KC_NUMPAD8:
			return '8';
			break;
		case OIS::KC_NUMPAD9:
			return '9';
			break;
		case OIS::KC_SUBTRACT:
			return '-';
			break;
		case OIS::KC_NUMPAD4:
			return '4';
			break;
		case OIS::KC_NUMPAD5:
			return '5';
			break;
		case OIS::KC_NUMPAD6:
			return '6';
			break;
		case OIS::KC_ADD:
			return '+';
			break;
		case OIS::KC_NUMPAD1:
			return '1';
			break;
		case OIS::KC_NUMPAD2:
			return '2';
			break;
		case OIS::KC_NUMPAD3:
			return '3';
			break;
		case OIS::KC_NUMPAD0:
			return '0';
			break;
		case OIS::KC_DECIMAL:
			return '.';
			break;
		case OIS::KC_NUMPADEQUALS:
			return '=';
			break;
		case OIS::KC_AT:
			return '@';
			break;
		case OIS::KC_COLON:
			return ':';
			break;
		case OIS::KC_UNDERLINE:
			return '_';
			break;
		case OIS::KC_DIVIDE:
			return '/';
			break;
		}

		// If the incoming key isn't one of AntTweakBar's Special Keys, return the ASCII value
		return keyEvent.text;
	}
}; // End Namespace
